import numpy as np
import matplotlib.pyplot as plt

# 多层感知器类定义
class NeuralNetwork:
    def __init__(self, input_size, hidden_size, output_size):
        self.weights_input_hidden = np.random.randn(input_size, hidden_size)
        self.bias_input_hidden = np.zeros((1, hidden_size))
        self.weights_hidden_output = np.random.randn(hidden_size, output_size)
        self.bias_hidden_output = np.zeros((1, output_size))

    def sigmoid(self, x):
        return 1 / (1 + np.exp(-x))

    def sigmoid_derivative(self, x):
        return x * (1 - x)

    def train(self, X, y, epochs, learning_rate):
        for epoch in range(epochs):
            hidden_layer_input = np.dot(X, self.weights_input_hidden) + self.bias_input_hidden
            hidden_layer_output = self.sigmoid(hidden_layer_input)
            output_layer_input = np.dot(hidden_layer_output, self.weights_hidden_output) + self.bias_hidden_output
            predicted_output = self.sigmoid(output_layer_input)

            error = y - predicted_output

            d_predicted_output = error * self.sigmoid_derivative(predicted_output)
            error_hidden_layer = d_predicted_output.dot(self.weights_hidden_output.T)
            d_hidden_layer = error_hidden_layer * self.sigmoid_derivative(hidden_layer_output)

            self.weights_hidden_output += hidden_layer_output.T.dot(d_predicted_output) * learning_rate
            self.bias_hidden_output += np.sum(d_predicted_output, axis=0, keepdims=True) * learning_rate
            self.weights_input_hidden += X.T.dot(d_hidden_layer) * learning_rate
            self.bias_input_hidden += np.sum(d_hidden_layer, axis=0, keepdims=True) * learning_rate

    def predict(self, X):
        hidden_layer_input = np.dot(X, self.weights_input_hidden) + self.bias_input_hidden
        hidden_layer_output = self.sigmoid(hidden_layer_input)
        output_layer_input = np.dot(hidden_layer_output, self.weights_hidden_output) + self.bias_hidden_output
        predicted_output = self.sigmoid(output_layer_input)
        return predicted_output

# 生成双月形状的数据
def generate_moon_data(samples, radius, width, horizontal_distance, vertical_distance):
    points = samples // 2
    noise = np.random.randn(points, 2)

    angle = np.random.rand(points) * np.pi
    
    # 控制左半月的数据生成
    x1 = radius * np.cos(angle) - horizontal_distance + noise[:, 0]
    y1 = radius * np.sin(angle) + vertical_distance + noise[:, 1]

    # 控制右半月的数据生成
    x2 = radius * np.cos(angle + np.pi) + horizontal_distance + noise[:, 0]
    y2 = radius * np.sin(angle + np.pi) - vertical_distance + noise[:, 1]

    # 合并左右半月的数据
    X = np.vstack((np.hstack((x1, x2)), np.hstack((y1, y2)))).T
    y = np.hstack((np.zeros(points), np.ones(points)))

    return X, y

# 绘制决策边界
def plot_decision_boundary(X, y, model, save_path=None):
    h = 0.01
    x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)

    plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Spectral)
    plt.contour(xx, yy, Z, levels=[0.5], colors='black')  # 绘制决策边界

    plt.title('MLP Classification on Moon Dataset')
    plt.xlabel('X1')
    plt.ylabel('X2')
    
    if save_path:
        plt.savefig(save_path)  # 保存图片到指定路径
    else:
        plt.show()

# 主函数
def main(horizontal_distance=5, vertical_distance=5, save_path=None):
    samples = 1000
    radius = 10
    width = 4

    # 生成双月形状的数据
    X, y = generate_moon_data(samples, radius, width, horizontal_distance, vertical_distance)

    input_size = 2
    hidden_size = 4
    output_size = 1

    epochs = 1500
    learning_rate = 0.01

    # 创建并训练多层感知器模型
    model = NeuralNetwork(input_size, hidden_size, output_size)
    model.train(X, y.reshape(-1, 1), epochs, learning_rate)

    # 绘制决策边界并保存图片（如果指定了保存路径）
    plot_decision_boundary(X, y, model, save_path)

if __name__ == "__main__":
    horizontal_distance = 5  # 控制左右距离
    vertical_distance = -1    # 控制上下距离
    main(horizontal_distance=horizontal_distance, vertical_distance=vertical_distance, save_path='moon_classification.png')
